﻿using System;
using System.Security;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Security.Permissions;
using System.IO;
using System.Windows.Controls;

namespace Pfz.Drawing.Wpf
{
    /// <summary>
    /// This class holds a 32-bit WriteableBitmap, but its Lock methods return a LockedBitmap class, which can be disposed to unlock it,
    /// and also has some methods to access its pixels in a fast and safe manner.
    /// </summary>
    public class ManagedBitmap
    {
		/// <summary>
		/// Creates a new ManagedBitmap copying the given imagesource.
		/// </summary>
		/// <param name="bitmapSource"></param>
		public ManagedBitmap(BitmapSource bitmapSource)
		{
			if (bitmapSource == null)
				throw new ArgumentNullException("bitmapSource");

			int width = bitmapSource.PixelWidth;
			int height = bitmapSource.PixelHeight;
			double dpiX = bitmapSource.DpiX;
			double dpiY = bitmapSource.DpiY;

			var drawingVisual = new DrawingVisual();
			using(var drawingContext = drawingVisual.RenderOpen())
				drawingContext.DrawImage(bitmapSource, new Rect(0, 0, bitmapSource.Width, bitmapSource.Height));

			var renderBitmap = new RenderTargetBitmap(width, height, dpiX, dpiY, PixelFormats.Pbgra32);
			renderBitmap.Render(drawingVisual);

			WriteableBitmap = new WriteableBitmap(width, height, dpiX, dpiY, PixelFormats.Bgra32, null);
			Int32Rect rect = new Int32Rect(0, 0, width, height);

			using(var lockedBitmap = Lock())
			{
				int stride = lockedBitmap.Stride;
				renderBitmap.CopyPixels(rect, WriteableBitmap.BackBuffer, height*stride, stride);
				lockedBitmap.AddDirtyRect(rect);
			}
		}

        /// <summary>
        /// Creates a new ManagedBitmap over a writeableBitmap, which must be of PixelFormat Bgra32.
        /// </summary>
        public ManagedBitmap(WriteableBitmap writeableBitmap)
        {
            if (writeableBitmap == null)
                throw new ArgumentNullException("writeableBitmap");

            if (writeableBitmap.Format != PixelFormats.Bgra32)
                throw new ArgumentException("writeableBitmap.Format must be PixelFormats.Bgra32.", "writeableBitmap");

            WriteableBitmap = writeableBitmap;
        }

        /// <summary>
        /// Creates a new ManagedBitmap with the given size and dpi.
        /// </summary>
        public ManagedBitmap(int width, int height, double dpiX = 96, double dpiY = 96)
        {
            WriteableBitmap = new WriteableBitmap(width, height, dpiX, dpiY, PixelFormats.Bgra32, null);
        }

        /// <summary>
        /// Gets the WriteableBitmap generated by this class.
        /// </summary>
        public WriteableBitmap WriteableBitmap { get; private set; }

		internal static SecurityPermission _unmanagedCodePermission = new SecurityPermission(SecurityPermissionFlag.UnmanagedCode);

        /// <summary>
        /// Locks the bitmap. This method will wait for the bitmap to be unlocked, indefinitely, if it is already locked.
        /// </summary>
		[SecuritySafeCritical]
        public LockedBitmap Lock()
        {
			_unmanagedCodePermission.Assert();

            WriteableBitmap.Lock();
            try
            {
                return new LockedBitmap(this);
            }
            catch
            {
                WriteableBitmap.Unlock();
                throw;
            }
        }

        /// <summary>
        /// Tries to lock the bitmap. If the bitmap is locked, a LockedBitmap will be returned. If not, null will be returned.
        /// </summary>
		[SecuritySafeCritical]
        public LockedBitmap TryLock(Duration timeout)
        {
			_unmanagedCodePermission.Assert();

            if (!WriteableBitmap.TryLock(timeout))
                return null;

            try
            {
                return new LockedBitmap(this);
            }
            catch
            {
                WriteableBitmap.Unlock();
                throw;
            }
        }

		/// <summary>
		/// Useful for medium-trust application that need to draw the image.
		/// </summary>
		public ImageSource CreateCopy()
		{
			var image = new Image();
			image.Source = WriteableBitmap;
			image.Arrange(new Rect(0, 0, 300, 300));
			var result = new RenderTargetBitmap(WriteableBitmap.PixelWidth, WriteableBitmap.PixelHeight, 96, 96, PixelFormats.Pbgra32);
			result.Render(image);
			return result;
		}
    }
}
